/*
 * Copyright 1999-2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.twdata.pipeline.stage;

import org.twdata.pipeline.*;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

import javax.xml.transform.*;
import javax.xml.transform.sax.*;
import javax.xml.transform.stream.*;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import java.util.Date;
import java.util.Map;

import org.apache.commons.logging.*;

/**
 * This is a transformer that logs all sax events it gets into a readable form 
 * to a file, mainly for debugging purposes.  This class was originally 
 * borrowed from Apache Cocoon.
 *
 * This transformers writes the SAX events in two styles - events mode and dom
 * mode.  In events mode, SAX events are logged as they are received, but it is
 * difficult to determine the overall structure of an XML document.  In DOM 
 * mode, the entire DOM is written out in a readable form after the SAX 
 * processing is complete.
 */
public class LogTransformer
  extends AbstractTransformer {

    private static String lf = System.getProperty("line.separator", "\n");
    private final static Log log = LogFactory.getLog(LogTransformer.class);

    private FileWriter logfile;
    private String logPath;
    private boolean append = false;
    private boolean events = false;
    private TransformerHandler handler;


    /**
     *  Sets up the file to write SAX events to
     *
     *@param  ctx                    The context
     *@exception  PipelineException  If anything goes wrong
     */
    public void setup(PipelineContext ctx)
        throws PipelineException {
        super.setup(ctx);

        try {
            File file = null;
            SourceResolver resolver = ctx.getSourceResolver();
            if (resolver != null) {
                URL url = resolver.resolve(logPath);
                if (url != null) {
                    if ("file".equals(url.getProtocol())) {
                        file = new File(url.getPath());
                    } else {
                        log.debug("Log path is not a file: "+url+" trying as an absolute path");
                        file = new File(logPath);
                    }    
                } else {
                    throw new PipelineException("Unable to resolve "+logPath);
                }
            } else {
                file = new File(logPath);
            }    
            this.logfile = new FileWriter(file, append);
        } catch (Exception ex) {
            throw new PipelineException("Unable to initialize log file", ex);
        } 

        if (events) {
            Date date = new Date();
            StringBuffer logEntry = new StringBuffer();
            logEntry.append ( "---------------------------- [" );
            logEntry.append ( date.toString() );
            logEntry.append ( "] ----------------------------" );
            this.log("setup", logEntry.toString());
        } else {
            try {
                SAXTransformerFactory factory = (SAXTransformerFactory) TransformerFactory.newInstance();
                handler = factory.newTransformerHandler();
                StreamResult result = new StreamResult(logfile);
                handler.setResult(result);
            } catch (Exception ex) {
                throw new PipelineException(ex);
            }
        }
    }

    /**
     *  Sets the path for the log file
     *
     * @param logPath The uri
     */
    public void setLogPath(String logPath) {
        this.logPath = logPath;
    }

    /**
     *  Sets whether the log file should be appended to
     *
     * @param append True to append 
     */
    public void setAppend(boolean append) {
        this.append = append;
    }    
 
    /**
     *  Sets whether be in events mode or not
     *
     * @param events True for event mode, false for DOM
     */
    public void setEvents(boolean events) {
        this.events = events;
    }    
   
    /**
     * Recycle
     */
    public void recycle() {
        super.recycle();
        try {
            if (this.logfile != null) logfile.close();
        } catch (Exception e) {
           // ignore
        }
        this.logfile = null;
        this.logPath = null;
        handler = null;
        append = false;
        events = false;
    }

    /**
     * Receive an object for locating the origin of SAX document events.
     */
    public void setDocumentLocator(Locator locator) {
        if (events) {
            this.log("setDocumentLocator", locator != null ? "systemid="+locator.getSystemId()+",publicid="+locator.getPublicId() : "(locator is null)");
        } else {
            handler.setDocumentLocator(locator);
        }    
        if (super.contentHandler!=null) {
            super.contentHandler.setDocumentLocator(locator);
        }
    }

    /**
     * Receive notification of the beginning of a document.
     */
    public void startDocument()
    throws SAXException {
        if (events) {
            this.log("startDocument", "");
        } else {
            handler.startDocument();
        }    
        if (super.contentHandler!=null) {
            super.contentHandler.startDocument();
        }
    }

    /**
     * Receive notification of the end of a document.
     */
    public void endDocument()
    throws SAXException {
        if (events) {
            this.log ("endDocument", "");
        } else {
            handler.endDocument();
        }    
        if (super.contentHandler!=null) {
            super.contentHandler.endDocument();
        }
    }

    /**
     * Begin the scope of a prefix-URI Namespace mapping.
     */
    public void startPrefixMapping(String prefix, String uri)
    throws SAXException {
        if (events) {
            this.log ("startPrefixMapping", "prefix="+prefix+",uri="+uri);
        } else {
            handler.startPrefixMapping(prefix, uri);
        }    
        if (super.contentHandler!=null) {
            super.contentHandler.startPrefixMapping(prefix,uri);
        }
    }

    /**
     * End the scope of a prefix-URI mapping.
     */
    public void endPrefixMapping(String prefix)
    throws SAXException {
        if (events) {
            this.log ("endPrefixMapping", "prefix="+prefix);
        } else {
            handler.endPrefixMapping(prefix);
        }    
        if (super.contentHandler!=null) {
            super.contentHandler.endPrefixMapping(prefix);
        }
    }

    /**
     * Receive notification of the beginning of an element.
     */
    public void startElement(String uri, String loc, String raw, Attributes a)
    throws SAXException {
        if (events) {
            this.log ("startElement", "uri="+uri+",local="+loc+",raw="+raw);
            for (int i = 0; i < a.getLength(); i++) {
                this.log ("            ", new Integer(i+1).toString()
                     +". uri="+a.getURI(i)
                     +",local="+a.getLocalName(i)
                     +",qname="+a.getQName(i)
                     +",type="+a.getType(i)
                     +",value="+a.getValue(i));
            }
        } else {
            handler.startElement(uri, loc, raw, a);
        }    
        if (super.contentHandler!=null) {
            super.contentHandler.startElement(uri,loc,raw,a);
        }
    }


    /**
     * Receive notification of the end of an element.
     */
    public void endElement(String uri, String loc, String raw)
    throws SAXException {
        if (events) {
            this.log ("endElement", "uri="+uri+",local="+loc+",raw="+raw);
        } else {
            handler.endElement(uri, loc, raw);
        }    
        if (super.contentHandler!=null) {
            super.contentHandler.endElement(uri,loc,raw);
        }
    }

    /**
     * Receive notification of character data.
     */
    public void characters(char ch[], int start, int len)
    throws SAXException {
        if (events) {
            this.log ("characters", new String(ch,start,len));
        } else {
            handler.characters(ch, start, len);
        }    
        if (super.contentHandler!=null) {
            super.contentHandler.characters(ch,start,len);
        }
    }

    /**
     * Receive notification of ignorable whitespace in element content.
     */
    public void ignorableWhitespace(char ch[], int start, int len)
    throws SAXException {
        if (events) {
            this.log ("ignorableWhitespace", new String(ch,start,len));
        } else {
            handler.ignorableWhitespace(ch, start, len);
        }    
        if (super.contentHandler!=null) {
            super.contentHandler.ignorableWhitespace(ch,start,len);
        }
    }

    /**
     * Receive notification of a processing instruction.
     */
    public void processingInstruction(String target, String data)
    throws SAXException {
        if (events) {
            log ("processingInstruction", "target="+target+",data="+data);
        } else {
            handler.processingInstruction(target, data);
        }    
        if (super.contentHandler!=null) {
            super.contentHandler.processingInstruction(target,data);
        }
    }

    /**
     * Receive notification of a skipped entity.
     */
    public void skippedEntity(String name)
    throws SAXException {
        if (events) {
            this.log ("skippedEntity", "name="+name);
        } else {
            handler.skippedEntity(name);
        }    
        if (super.contentHandler!=null) {
            super.contentHandler.skippedEntity(name);
        }
    }

    /**
     * Report the start of DTD declarations, if any.
     */
    public void startDTD(String name, String publicId, String systemId)
    throws SAXException {
        if (events) {
            this.log ("startDTD", "name="+name+",publicId="+publicId+",systemId="+systemId);
        } else {
            handler.startDTD(name, publicId, systemId);
        }    
        if (lexicalHandler!=null) {
            lexicalHandler.startDTD(name,publicId,systemId);
        }
    }

    /**
     * Report the end of DTD declarations.
     */
    public void endDTD()
    throws SAXException {
        if (events) {
            this.log ("endDTD", "");
        } else {
            handler.endDTD();
        }    
        if (lexicalHandler!=null) {
            lexicalHandler.endDTD();
        }
    }

    /**
     * Report the beginning of an entity.
     */
    public void startEntity(String name)
    throws SAXException {
        if (events) {
            this.log ("startEntity", "name="+name);
        } else {
            handler.startEntity(name);
        }    
        if (lexicalHandler!=null) {
            lexicalHandler.startEntity(name);
        }
    }

    /**
     * Report the end of an entity.
     */
    public void endEntity(String name)
    throws SAXException {
        if (events) {
            this.log ("endEntity", "name="+name);
        } else {
            handler.endEntity(name);
        }    
        if (lexicalHandler!=null) {
            lexicalHandler.endEntity(name);
        }
    }

    /**
     * Report the start of a CDATA section.
     */
    public void startCDATA()
    throws SAXException {
        if (events) {
            this.log ("startCDATA", "");
        } else {
            handler.startCDATA();
        }    
        if (lexicalHandler!=null) {
            lexicalHandler.startCDATA();
        }
    }

    /**
     * Report the end of a CDATA section.
     */
    public void endCDATA()
    throws SAXException {
        if (events) {
            this.log ("endCDATA", "");
        } else {
            handler.endCDATA();
        }    
        if (lexicalHandler!=null) {
            lexicalHandler.endCDATA();
        }
    }

    /**
     * Report an XML comment anywhere in the document.
     */
    public void comment(char ch[], int start, int len)
    throws SAXException {
        if (events) {
            this.log ("comment", new String(ch,start,len));
        } else {
            handler.comment(ch, start, len);
        }    
        if (lexicalHandler!=null) {
            lexicalHandler.comment(ch,start,len);
        }
    }

    /**
     * Report to logfile.
     */
    private void log (String location, String description) {
        final StringBuffer logEntry = new StringBuffer();
        logEntry.append ( "[" );
        logEntry.append ( location );
        logEntry.append ( "] " );
        logEntry.append ( description );
        logEntry.append ( lf );
        final String text = logEntry.toString();
        //if ( this.getLogger().isInfoEnabled() ) {
        //    this.getLogger().info( text );
        //}
        try {
            if ( null != this.logfile ) {
                this.logfile.write( text, 0, text.length());
                this.logfile.flush();
            } else {
                System.out.println( text );
            }
        }
        catch(IOException ioe) {
            //this.getLogger().debug("LogTransformer.log", ioe);
            // ignore for now
        }
    }
}
